﻿using System;
using System.Threading;
using System.Threading.Tasks;

using StackExchange.Redis;

namespace RedisLib
{
    public partial class RedisClient
    {
        private readonly SemaphoreSlim LockTakeSemaphoreSlim = new SemaphoreSlim(1, 1);

        public async Task<bool> Lock(string lockKey, TimeSpan expiry, Action job, string lockToken = null, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            // token用来标识谁拥有该锁并用来释放锁。
            lockToken ??= Environment.MachineName;
            RedisValue token = lockToken;
            // lock_key表示的是redis数据库中该锁的名称，不可重复。
            RedisKey redisLockKey = ConvertStringKeyToRedisKey(lockKey);
            // TimeSpan表示该锁的有效时间。超时后自动释放，避免死锁。

            try
            {
                if (await LockTakeSemaphoreSlim.WaitAsync(TimeSpan.FromSeconds(1), cancellationToken) == false)
                    return false;

                if (await database.LockTakeAsync(redisLockKey, token, expiry) == false)
                    return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return false;
            }
            finally
            {
                LockTakeSemaphoreSlim.Release();
            }

            try
            {
                job();
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return false;
            }
            finally
            {
                await database.LockReleaseAsync(redisLockKey, token);
            }
        }

        public async Task<bool> Lock(string lockKey, TimeSpan expiry, Func<Task> job, string lockToken = null, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            // token用来标识谁拥有该锁并用来释放锁。
            lockToken ??= Environment.MachineName;
            RedisValue token = lockToken;
            // lock_key表示的是redis数据库中该锁的名称，不可重复。
            RedisKey redisLockKey = ConvertStringKeyToRedisKey(lockKey);
            // TimeSpan表示该锁的有效时间。超时后自动释放，避免死锁。

            try
            {
                if (await LockTakeSemaphoreSlim.WaitAsync(TimeSpan.FromSeconds(1), cancellationToken) == false)
                    return false;

                if (await database.LockTakeAsync(redisLockKey, token, expiry) == false)
                    return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return false;
            }
            finally
            {
                LockTakeSemaphoreSlim.Release();
            }

            try
            {
                await job();
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return false;
            }
            finally
            {
                await database.LockReleaseAsync(redisLockKey, token);
            }
        }

        public async Task<bool> LockWithExtend(string lockKey, TimeSpan expiry, Action job, string lockToken = null, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            // token用来标识谁拥有该锁并用来释放锁。
            lockToken ??= Environment.MachineName;
            RedisValue token = lockToken;
            // lock_key表示的是redis数据库中该锁的名称，不可重复。
            RedisKey redisLockKey = ConvertStringKeyToRedisKey(lockKey);
            // TimeSpan表示该锁的有效时间。超时后自动释放，避免死锁。
            TimeSpan extendTimeout = TimeSpan.FromMilliseconds(expiry.TotalMilliseconds / 2);

            try
            {
                if (await LockTakeSemaphoreSlim.WaitAsync(extendTimeout, cancellationToken) == false)
                    return false;

                if (await database.LockTakeAsync(redisLockKey, token, expiry) == false)
                    return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return false;
            }
            finally
            {
                LockTakeSemaphoreSlim.Release();
            }

            #region Lock Extend
            CancellationTokenSource cancellationTokenSource;
            if (cancellationToken != default)
            {
                cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            }
            else
            {
                cancellationTokenSource = new CancellationTokenSource();
            }

            async Task lockExtend()
            {
                while (cancellationTokenSource.IsCancellationRequested == false)
                {
                    try
                    {
                        await Task.Delay(extendTimeout, cancellationTokenSource.Token);
                    }
                    catch { }

                    if (cancellationTokenSource.IsCancellationRequested) break;
                    await database.LockExtendAsync(lockKey, token, expiry);
                }
            }
            #endregion

            try
            {
                Task lockExtendTask = lockExtend();
                job();

                if (cancellationTokenSource.IsCancellationRequested == false)
                    cancellationTokenSource.Cancel();
                await lockExtendTask;
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                if (cancellationTokenSource.IsCancellationRequested == false)
                    cancellationTokenSource.Cancel();
                return false;
            }
            finally
            {
                if (cancellationTokenSource.IsCancellationRequested == false)
                    cancellationTokenSource.Cancel();
                cancellationTokenSource.Dispose();
                await database.LockReleaseAsync(redisLockKey, token);
            }
        }

        public async Task<bool> LockWithExtend(string lockKey, TimeSpan expiry, Func<Task> job, string lockToken = null, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            // token用来标识谁拥有该锁并用来释放锁。
            lockToken ??= Environment.MachineName;
            RedisValue token = lockToken;
            // lock_key表示的是redis数据库中该锁的名称，不可重复。
            RedisKey redisLockKey = ConvertStringKeyToRedisKey(lockKey);
            // TimeSpan表示该锁的有效时间。超时后自动释放，避免死锁。
            TimeSpan extendTimeout = TimeSpan.FromMilliseconds(expiry.TotalMilliseconds / 2);

            try
            {
                if (await LockTakeSemaphoreSlim.WaitAsync(extendTimeout, cancellationToken) == false)
                    return false;

                if (await database.LockTakeAsync(redisLockKey, token, expiry) == false)
                    return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return false;
            }
            finally
            {
                LockTakeSemaphoreSlim.Release();
            }

            #region Lock Extend
            CancellationTokenSource cancellationTokenSource;
            if (cancellationToken != default)
            {
                cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            }
            else
            {
                cancellationTokenSource = new CancellationTokenSource();
            }

            async Task lockExtend()
            {
                while (cancellationTokenSource.IsCancellationRequested == false)
                {
                    try
                    {
                        await Task.Delay(extendTimeout, cancellationTokenSource.Token);
                    }
                    catch { }

                    if (cancellationTokenSource.IsCancellationRequested) break;
                    await database.LockExtendAsync(redisLockKey, token, expiry);
                }
            }
            #endregion

            try
            {

                Task lockExtendTask = lockExtend();
                await job();

                if (cancellationTokenSource.IsCancellationRequested == false)
                    cancellationTokenSource.Cancel();
                await lockExtendTask;
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);

                if (cancellationTokenSource.IsCancellationRequested == false)
                    cancellationTokenSource.Cancel();
                return false;
            }
            finally
            {
                if (cancellationTokenSource.IsCancellationRequested == false)
                    cancellationTokenSource.Cancel();
                cancellationTokenSource.Dispose();
                await database.LockReleaseAsync(redisLockKey, token);
            }
        }
    }
}
